package com.haiertvbic.tvnow.service;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Timer;
import java.util.TimerTask;

public final class SocketClient {
	public static final int SOCKET_STATUS_CLOSED = 0x0000;
	public static final int SOCKET_STATUS_CONNECTED = 0x0001;
	public static final int SOCKET_STATUS_READY = 0x0002;

	public static final int SOCKET_ERRORS_NONE = 0x0800;
	public static final int SOCKET_ERRORS_UNKNOWN_HOST = 0x0801;
	public static final int SOCKET_ERRORS_IO = 0x0802;
	public static final int SOCKET_ERRORS_EMPTY_DATA = 0x0803;
	public static final int SOCKET_ERRORS_INVALID_BUFFER = 0x0804;
	public static final int SOCKET_ERRORS_NOT_READY = 0x0805;
	public static final int SOCKET_ERRORS_DISCONNECT = 0x0806;

	private static final int READING_TIMEOUT_SECONDS = 10;
	private static final int TICK_INTERVAL = 100;

	private boolean isReading = false;
	private int timeout_reading = READING_TIMEOUT_SECONDS;
	private int timeout_reading_tick = 0;

	private final Timer timer = new Timer();
	private final TimerTask timerTask = new TimerTask() {
		@Override
		public void run() {
			if (isReading) {
				timeout_reading_tick += 1;
				if (timeout_reading_tick >= timeout_reading * 10) {
					isReading = false;
					synchronized (this) {
						if (client != null) {
							try {
								client.shutdownInput();
							} catch (Exception e) {
							}

							try {
								client.shutdownOutput();
							} catch (Exception e) {
							}
						}
					}
					disconnect();
					timeout_reading_tick = 0;
				}
			} else
				timeout_reading_tick = 0;
		}
	};

	public static String getErrorsMessage(int errors) {
		String msg = "";
		switch (errors) {
		case SOCKET_ERRORS_NONE:
			msg = "Can not connect to server";
			break;
		case SOCKET_ERRORS_UNKNOWN_HOST:
			msg = "Unknown host error";
			break;
		case SOCKET_ERRORS_IO:
			msg = "Input or Output error";
			break;
		case SOCKET_ERRORS_EMPTY_DATA:
			msg = "Sending empty data error";
			break;
		case SOCKET_ERRORS_INVALID_BUFFER:
			msg = "Invalid data buffer error";
			break;
		case SOCKET_ERRORS_NOT_READY:
			msg = "Client is not ready";
			break;
		case SOCKET_ERRORS_DISCONNECT:
			msg = "Client is disconnect";
			break;
		}
		return msg;
	}

	private String host = null;
	private int port = -1;
	private Socket client = null;
	private BufferedOutputStream output = null;
	private BufferedInputStream input = null;
	private int status = SOCKET_STATUS_CLOSED;
	private int errors = SOCKET_ERRORS_NONE;

	public SocketClient(String host, int port) {
		this.host = host;
		this.port = port;
		new Thread(new Runnable() {
			@Override
			public void run() {
				timer.schedule(timerTask, TICK_INTERVAL, TICK_INTERVAL);
			}
		}).start();
	}

	public BufferedInputStream getInputStream() {
		return input;
	}

	public int getStatus() {
		return status;
	}

	public int getErrors() {
		return errors;
	}

	public void connect() {
		disconnect();
		try {
			client = new Socket(host, port);
			status = SOCKET_STATUS_CONNECTED;
		} catch (UnknownHostException e) {
			errors = SOCKET_ERRORS_UNKNOWN_HOST;
		} catch (IOException e) {
			errors = SOCKET_ERRORS_IO;
		} catch (Exception e) {
		}

		if (status == SOCKET_STATUS_CONNECTED) {
			try {
				output = new BufferedOutputStream(client.getOutputStream());
				input = new BufferedInputStream(client.getInputStream());
				status = SOCKET_STATUS_READY;
			} catch (IOException e) {
				disconnect();
				errors = SOCKET_ERRORS_IO;
			} catch (Exception e) {
			}
		}
	}

	public int sendData(byte[] data) {
		errors = SOCKET_ERRORS_NONE;
		if (data == null || data.length == 0) {
			errors = SOCKET_ERRORS_EMPTY_DATA;
			return 0;
		}
		int result = data.length;
		if (status == SOCKET_STATUS_READY) {
			try {
				output.write(data);
				output.flush();
			} catch (IOException e) {
				errors = SOCKET_ERRORS_IO;
				result = 0;
			} catch (Exception e) {
			}
		} else {
			errors = SOCKET_ERRORS_NOT_READY;
			result = 0;
		}
		return result;
	}

	public synchronized void beginRead() {
		isReading = true;
		timeout_reading_tick = 0;
	}

	public synchronized void endRead() {
		isReading = false;
		timeout_reading_tick = 0;
	}

	public int receiveData(byte[] buffer) {
		errors = SOCKET_ERRORS_NONE;
		if (buffer == null) {
			errors = SOCKET_ERRORS_INVALID_BUFFER;
			return 0;
		}
		int result = 0;
		if (status == SOCKET_STATUS_READY) {
			beginRead();
			try {
				result = input.read(buffer);
				if (result == -1)
					errors = SOCKET_ERRORS_DISCONNECT;
			} catch (IOException e) {
				errors = SOCKET_ERRORS_IO;
				result = -1;
			} catch (Exception e) {
			}
			endRead();
		}
		return result;
	}

	public synchronized void disconnect() {
		try {
			if (input != null) {
				input.close();
				input = null;
			}
		} catch (Exception e) {
		}

		try {
			if (output != null) {
				output.close();
				output = null;
			}
		} catch (Exception e) {
		}

		if (client != null) {
			try {
				client.shutdownInput();
			} catch (Exception e) {
			}

			try {
				client.shutdownOutput();
			} catch (Exception e) {
			}
		}

		try {
			if (client != null) {
				client.close();
				client = null;
			}
		} catch (Exception e) {
		} finally {
			status = SOCKET_STATUS_CLOSED;
			errors = SOCKET_ERRORS_NONE;
			isReading = false;
		}
	}

	public void release() {
		isReading = false;
		disconnect();
		timer.cancel();
	}
}
